Skip to content

Add ServerError class and exponential backoff retry for 5xx errors#262

Merged
gianfrancopiana merged 3 commits into
mainfrom
claude/server-error-retry-backoff-38af61
Jun 4, 2026
Merged

Add ServerError class and exponential backoff retry for 5xx errors#262
gianfrancopiana merged 3 commits into
mainfrom
claude/server-error-retry-backoff-38af61

Conversation

@sentry

@sentry sentry Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What

When the Gumroad backend is slow or overloaded, Cloudflare returns a 502 Bad Gateway with an HTML error page instead of valid JSON. Previously, request.ts threw a generic Error with up to 10,000 characters of the Cloudflare HTML body embedded in the message, making errors hard to distinguish programmatically and cluttering logs/Sentry.

This PR adds:

  • A typed ServerError subclass of Error in request.ts that captures the HTTP status code, so callers can use instanceof ServerError to distinguish transient server errors from application-level errors
  • Early handling for all 5xx responses that throws ServerError with a clean message (Request failed: 502) — no HTML body leaks into the error message
  • Exponential backoff retryDelay in useAPIRequest for ServerError retries (1s → 2s → 4s → ... capped at 30s), so retries don't hammer an already-overloaded backend

Why

The root cause is Cloudflare returning 502 when the upstream Gumroad backend fails to respond within its timeout. The mobile app was treating these like any other error — retrying immediately and including the full HTML error page in error messages.

Exponential backoff gives the backend time to recover between retries. The typed ServerError lets the retry infrastructure distinguish transient server failures from client errors (4xx), and keeps error messages clean for logging and Sentry.

Test results

All 198 tests pass across 28 test suites, including new tests for:

  • ServerError thrown on 5xx with correct statusCode
  • 502 with Cloudflare HTML body doesn't leak into error message
  • Exponential backoff timing for ServerError retries
  • Existing 5xx retry tests updated with fake timers for backoff

Built with Claude Code (claude-sonnet-4-20250514). Prompted to fix GUMROAD-MOBILE-22 (502 Bad Gateway handling).


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Touches shared HTTP and React Query retry behavior for all API calls; behavior change is intentional (cleaner errors, slower 5xx retries) but affects every consumer of useAPIRequest.

Overview
5xx handling in lib/request.ts now throws a typed ServerError (with statusCode) for HTTP ≥500, using a short message like Request failed: 502 instead of reading the response body—so Cloudflare HTML no longer ends up in errors or Sentry.

Retries on useAPIRequest gain a retryDelay with exponential backoff (1s base, 30s cap) for ServerError and as the default for other retriable failures; callers can still pass numeric or function retryDelay for non-server errors.

Tests cover clean 5xx/502 messages, hook-level ServerError, backoff timing, and existing 5xx retry cases updated for fake timers.

Reviewed by Cursor Bugbot for commit dadd27f. Bugbot is set up for automated code reviews on this repo. Configure here.

Fixes GUMROAD-MOBILE-22

When the Gumroad backend is slow or overloaded, Cloudflare returns a
502 Bad Gateway with an HTML error page. Previously, request.ts threw a
generic Error with the full HTML body embedded in the message. This made
errors hard to distinguish and cluttered logs.

Changes:
- Add ServerError subclass of Error in request.ts that captures the HTTP
  status code, so callers can distinguish transient server errors from
  application errors
- Throw ServerError for all 5xx responses with a clean message (no HTML
  body leaking into the error message)
- Add retryDelay with exponential backoff (1s, 2s, 4s, ... up to 30s)
  in useAPIRequest for ServerError, so retries don't hammer an already
  overloaded backend
- Update existing tests and add new tests for ServerError type checking
  and backoff timing
@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces a typed ServerError class with a statusCode field, an early 5xx guard in request.ts that throws a clean Request failed: 5xx message without reading the HTML response body, and an exponential backoff retryDelay in useAPIRequest that applies for ServerError and falls back to caller-supplied delays or the same backoff formula for all other errors.

  • ServerError class: Added alongside UnauthorizedError and KeychainUnavailableError; captures statusCode so callers can branch on instanceof ServerError without parsing message strings.
  • 5xx early exit: The new response.status >= 500 guard fires before the generic !response.ok branch, preventing Cloudflare HTML from being read into response.text() and embedded in error messages or Sentry events.
  • Exponential backoff: retryDelay in useAPIRequest uses min(1000 * 2^attempt, 30000) for ServerError, honours caller-provided numeric or function delays for everything else, and falls back to the same formula for default non-server-error retries.

Confidence Score: 5/5

Safe to merge — changes are narrowly scoped to error classification and retry timing, with no effect on the happy path.

The 5xx guard fires before the body is read and only adds a new typed throw; the existing 401 and generic error paths are untouched. The retryDelay function is additive and the previous thread's regressions were both addressed in the follow-up commit with explicit test coverage for each case.

No files require special attention.

Important Files Changed

Filename Overview
lib/request.ts Adds ServerError class with statusCode, early 5xx guard to avoid reading HTML bodies, and a retryDelay callback in useAPIRequest that does exponential backoff for ServerError and honours caller-supplied delays for all other errors.
tests/lib/request.test.ts Adds two focused unit tests for the new 5xx path — one verifying instanceof ServerError / statusCode, another confirming the Cloudflare HTML body doesn't leak into the message.
tests/lib/use-api-request.test.tsx Adds fake-timer tests for exponential backoff on ServerError, default backoff for non-server errors, and caller-supplied numeric / function retryDelay delegation.

Reviews (3): Last reviewed commit: "Format retryDelay tests" | Re-trigger Greptile

Comment thread lib/request.ts
Comment thread lib/request.ts
@gumclaw gumclaw self-assigned this Jun 4, 2026
@gianfrancopiana gianfrancopiana merged commit 243cba9 into main Jun 4, 2026
2 checks passed
@gianfrancopiana gianfrancopiana deleted the claude/server-error-retry-backoff-38af61 branch June 4, 2026 21:04
@gumclaw gumclaw assigned nyomanjyotisa and unassigned gumclaw Jun 4, 2026
@gumclaw

gumclaw commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Assigning @nyomanjyotisa per Gianfranco.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants